package branch_and_price;

import ilog.concert.*;
import ilog.cplex.IloCplex;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;

// 列生成的解模型
public class Master {
    private Data data;
    private Pool pool;
    // 列表存储所有路径的索引
    private ArrayList<Integer> route_index;
    private HashSet<Integer> route_hashset;
    private double cost;
    // 最终返回的解（最终返回的解应该是几天TSP路径）
    private ArrayList<Integer> solution;

    //---------------------------------cut information--------------------------------------------------------

    // 存储每条路径的成本
    private double r_cost[];

    // 决策变量
    private IloNumVar var[];

    // constraints: every customer is visited
    // the row index of request constraint in matrix
    // 存储行号的数组（每一行代表一个约束条件）
    private int[] rc;

    // 约束矩阵中的系数【coef[i][j]，i列，j行】
    // 假设在i列中，coef[i][j]=1，表示j点被访问
    private double[][] coef;

    // dual value set   2n(for each customer) + 1( for vehicle<=k)
    // 每个约束条件的对偶值
    private double[] dual;

    // route index to the position in the matrix
    // key为在路径池中的索引，value为在规划求解系数矩阵中的索引（防止路径池中出现重复路径）
    HashMap<Integer, Integer> ri2p;

    private IloCplex cplex; // 总模型
    private IloLPMatrix matrix; // 变量的系数矩阵

    public Master(Pool p) {
        this.data = p.getData();
        this.pool = p;
        dual = new double[data.getN()];
        ri2p = new HashMap<>();
    }

    //构建求解的cplex对象
    public void construct() throws IloException {
        cplex = new IloCplex();
        // Creates and returns an empty LP matrix object.
        matrix = cplex.LPMatrix();

        route_index = new ArrayList<>();
        route_hashset = new HashSet<>();

        rc = new int[data.getN()];

        //------------------------------------construct cplex model----------------------------------------
        // Selects the dual simplex algorithm.（第一个参数，第二个参数可能表示的是对偶的求解方法）
        cplex.setParam(IloCplex.IntParam.RootAlg, IloCplex.Algorithm.Dual);

        // objective is minimizing【目标表达式】
        IloLinearNumExpr obj = cplex.linearNumExpr();
        cplex.addMinimize(obj);

        // constraint every customer is visited【约束条件：每个客户都被访问，为此约束添加范围限制】
        for (int i = 0; i < data.getN(); i++) {
            // addRange Creates and returns an empty range object with the specified lower and upper bounds.
            rc[i] = matrix.addRow(cplex.addRange(1, 1, "R_P_" + i));
        }

        // using add_col to enter the route in pool【将初始解作为的列添加进去】
        ArrayList<Integer> col_index_set = new ArrayList<>();
        for (int i = 0; i < pool.getRoute_Pool().size(); i++) {
            col_index_set.add(i);
        }
        add_col(col_index_set);
    }

    /**
     * method to add variables into matrix and objective
     * 将pool中的路径，按照r_index_set的索引添加到求解模型中
     */
    public void add_col(ArrayList<Integer> r_index_set) throws IloException {

        System.out.println("--------------- 打印入列的路径信息 ---------------");
        for (int i = 0; i < r_index_set.size(); i++) {
            System.out.print("第"+r_index_set.get(i)+"列");
            Pool.showRoute(pool.getRoute_Pool().get(r_index_set.get(i)));
        }

        r_cost = new double[r_index_set.size()];
        // getNrows Returns the number of rows of the matrix representation of the active model being solved when the invoking callback is called.
        coef = new double[r_index_set.size()][cplex.getNrows()];
        var = new IloNumVar[r_index_set.size()];

        // r is not the route index, the content of rset(r) is index
        // 设置决策变量取值范围、类型、名称（路径池中的索引）
        for (int i = 0; i < r_index_set.size(); i++) {
            var[i] = cplex.numVar(0.0, Data.BigNum, pool.getRoute_Name().get(r_index_set.get(i)));
        }

        // 每条路径的成本，也就是目标函数中决策变量的系数
        for (int i = 0; i < r_index_set.size(); i++) {
            r_cost[i] = pool.getRoute_Cost().get(r_index_set.get(i));
        }

        // ------------------------------------put the visit customer by routes into coef[][]----------------
        // 记录每条路径每行的变量系数
        for (int i = 0; i < r_index_set.size(); i++) {
            // 取出一个TSP路径
            ArrayList<Integer> route = pool.getRoute_Pool().get(r_index_set.get(i));
            // 找出路径中的访问点
            int[] visit_count = new int[data.getN()];
            for (int j = 0; j < route.size(); j++) {
                visit_count[route.get(j)] = 1;
            }
            // 将访问点标记到矩阵中
            for (int j = 0; j < data.getN(); j++) {
                coef[i][rc[j]] = visit_count[j];
            }
        }

        //------------------------------------add variable to matrix-----------------------------------------
        // 将变量添加到系数矩阵中
        for (int i = 0; i < r_index_set.size(); i++) {
            // 计算 non_num，本列代表的TSP路径中已访问点的个数
            int non_num = 0;
            for (int j = 0; j < cplex.getNrows(); j++) {
                if (coef[i][j] == 1) {
                    non_num++;
                }
            }
            // 计算本列的系数（注意用到的place索引）
            int indices[] = new int[non_num]; // 本列中行系数的索引
            double val[] = new double[non_num]; // val[j]表示本列在j行的系数
            int place = 0;
            for (int j = 0; j < cplex.getNrows(); j++) {
                if (coef[i][j] == 1) {
                    indices[place] = j;
                    val[place] = coef[i][j];
                    place++;
                }
            }
            // 向矩阵中添加列
            int ret = matrix.addColumn(var[i], indices, val);
            ri2p.put(r_index_set.get(i), ret);
        }

        //----------------------------------add variable to objective----------------------------------------
        IloLinearNumExpr obj = (IloLinearNumExpr)cplex.getObjective().getExpr();
        for (int i = 0; i <r_index_set.size(); i++) {
            obj.addTerm(r_cost[i],var[i]);
        }
        cplex.getObjective().setExpr(obj);

    }

    /**
     * 使用列生成算法求解，并返回是否可以得到最优解
     */
    public boolean solve() throws IloException {
        // Sets the default output stream of the invoking CP object.
        // After this call, all logging output will be output via the new stream.
        // Passing null as the new output stream will turn off all logging output.
        cplex.setOut(null);

        if (cplex.solve()) {
            return true;
        } else {
            // 如果没有找到解，输出模型
            cplex.exportModel("unsolve_model.lp");
            return false;
        }
    }

    // get the dual values
    public void set_dual() throws IloException {
        for (int i = 0; i < data.getN(); i++) {
            // Returns the dual solution value for a constraint.
            dual[i] = cplex.getDual(matrix.getRange(rc[i]));
        }
    }

    // 打印解，打印出总成本，使用哪条路径，每条路径的系数，每条路径的成本
    public void show_sol() throws IloException {
        System.out.println("------------------------------------------------------- LP_result -------------------------------------------------");
        // 打印总成本
        double result = cplex.getObjValue();
        System.out.println("Objective Value = " + result);

        // 打印变量的路径
        for (int i = 0; i < cplex.getNcols(); i++) {
            double ans = cplex.getValue(matrix.getNumVar(i));
            if (ans > 0) {
                System.out.println(matrix.getNumVar(i).getName() + " " + ans);
                int index = Integer.parseInt(matrix.getNumVar(i).getName());
                Pool.showRoute(pool.getRoute_Pool().get(index));
            }
        }
    }

    public double[] getDual() {
        return dual;
    }

    public Pool getPool() {
        return pool;
    }
}